package joq;

import java.util.Collection;
import java.util.TreeMap;

/**
 * Manages tagged queues.
 * @author Jeff Ober<jeffober@gmail.com>
 */
public class MessageQueue
{
    /**
     * Stores tagged messages.
     */
    TreeMap<String, SkewHeap<Message>> tags;
    /**
     * Stores untagged messages.
     */
    SkewHeap<Message> untagged;

    /**
     * Creates a new message manager with empty queues.
     */
    public MessageQueue()
    {
	tags = new TreeMap<String, SkewHeap<Message>>();
	untagged = new SkewHeap<Message>();
    }

    /**
     * Returns true if there are no items in any queue.
     * @return true when there are no items in any queue
     */
    public boolean isEmpty()
    {
	return getQueue().isEmpty();
    }

    /**
     * Returns true when there are no items in a single queue.
     * @param tag a single tag
     * @return true when there are no items in a single queue
     */
    public boolean isEmpty(String tag)
    {
	return getQueue(tag).isEmpty();
    }

    /**
     * Returns true when there are no items in any queues identified by the tags
     * parameter.
     * @param tags a list of tags
     * @return true when there are no items in any queue identified by tags
     */
    public boolean isEmpty(Collection<String> tags)
    {
	for (String tag : tags)
	    if (isEmpty(tag))
		return true;
	return false;
    }

    /**
     * Prints out all queues, organized by tags. Useful for debugging difficult
     * merge issues.
     */
    public void explain()
    {
	System.out.println("Untagged");
	untagged.explain();
	System.out.println();

	for (String tag : tags.keySet()) {
	    System.out.format("Queue tagged %s%n", tag);
	    tags.get(tag).explain();
	    System.out.println();
	}
    }

    /**
     * Builds a single queue out of all queues.
     * @return a combined queue
     */
    protected SkewHeap<Message> getQueue()
    {
	SkewHeap<Message> q = untagged.shallowCopy();
	q.merge(getQueue(tags.keySet()));
	return q;
    }

    /**
     * Returns the queue for tag. If the queue has not yet been created, it is
     * created here.
     * @param tag
     * @return the queue for tag
     */
    protected SkewHeap<Message> getQueue(String tag)
    {
	if (!tags.containsKey(tag))
	    tags.put(tag, new SkewHeap<Message>());
	return tags.get(tag);
    }

    /**
     * Builds a queue for the collection of tags provided. If any of the queues
     * have not yet been created, they are created here.
     * @param tags
     * @return a combined queue
     */
    protected SkewHeap<Message> getQueue(Collection<String> tags)
    {
	SkewHeap<Message> q = new SkewHeap<Message>();
	for (String tag : tags)
	    q.merge(getQueue(tag).shallowCopy());
	return q;
    }

    /**
     * Adds msg to the queue, untagged.
     * @param msg
     */
    public void send(Message msg)
    {
	untagged.push(msg);
    }

    /**
     * Adds msg to the queue and tags it with one tag.
     * @param msg
     * @param tag
     */
    public void send(Message msg, String tag)
    {
	getQueue(tag).push(msg);
    }

    /**
     * Adds msg to the queue and tags it with multiple tags.
     * @param msg
     * @param tags
     */
    public void send(Message msg, Collection<String> tags)
    {
	for (String tag : tags)
	    send(msg, tag);
    }

    /**
     * Utility function to retrive one item from fromQueue and signal removal if
     * necessary.
     * @param fromQueue
     * @return a Message or null
     */
    private Message receiveFrom(SkewHeap<Message> fromQueue)
    {
	Message m = fromQueue.poll();
	if (m != null) // poll returns null on empty
	    m.signalRemoval();
	return m;
    }

    /**
     * Receives one Message from any queue, or null if there are no Messages
     * waiting.
     * @return a Message or null
     */
    public Message receive()
    {
	return receiveFrom(getQueue());
    }

    /**
     * Receives one Message from a single queue, or null if there are no
     * Messages waiting.
     * @return a Message or null
     */
    public Message receive(String tag)
    {
	return receiveFrom(getQueue(tag));
    }

    /**
     * Receives one Message from several queues, or null if there are no
     * Messages waiting.
     * @return a Message or null
     */
    public Message receive(Collection<String> tags)
    {
	return receiveFrom(getQueue(tags));
    }
}
